import math
import const
import sys
import os

sys.path.append(os.path.dirname(os.path.realpath(__file__)))

class hero:

	# Hero info
	commonName = ""	# Common name (what people actually call the hero)
	name = ""			# Name of the hero
	title = ""			# Title of the hero
	fullName = ""		# Name+Title combined

	heroAbilities = []	# Here you can put abilities associated
								# with the hero. They will not be automatically
								# added to the hero.

	# Constants for the hero
	
	baseDmgMin = 0			# Min damage (excluding primary attribute)
	baseDmgMax = 0			# Max damage
	baseArmor = 0			# Armor (excluding agi)
	baseSpellRes = .25	# Magic resistance
	baseHP = 150			# Base HP, this is 150 for all heroes in DotA
	baseMP = 0				# Base MP, zero for all heroes
	baseHPR = .25			# Base HP regen, .25 for most heroes
	baseMPR = .01			# Base 

	baseStr = 1	# Stats and stat gains
	baseAgi = 1
	baseInt = 1
	gainStr = 0
	gainAgi = 0
	gainInt = 0

	primaryAttr = const.attrStr # Primary attribute

	attackRange = 128	# Attack range and missile speed
	missileSpeed = 0
	weaponType = const.atkMelee
	

	BAT = 1.7 # BAT, 1.7 for most heroes

	attackFront = 0
	attackBack = 0
	castFront = 0
	castBack = 0

	daySight = 1800
	nightSight = 800



	def addModifier(self, modifier, dest):
		
		modifier.associatedHero = self
		dest.append(modifier)
		
		for buff in modifier.associatedBuffs:
			self.addModifier(buff, self.buffs)
		for ability in modifier.associatedAbilities:
			self.addModifier(ability, self.abilities)

	def addItem(self, item):
		self.addModifier(item, self.inventory)

	def addAbility(self, ability):
		self.addModifier(ability, self.abilities)

	def addBuff(self, buff):
		self.addModifier(buff, self.buffs)

	

	def getBonusStats(self, stat):
		return self.getStats(self.abilities, stat) + self.getStats(self.buffs, stat) + self.getStats(self.inventory, stat)

	def getStats(self, section, stat):
		c = 0
		for item in section:
			c += getattr(item, stat)
		return c

	def getMaxStats(self, stat):
		return max(self.getMaxStatsFrom(self.abilities, stat), self.getMaxStatsFrom(self.buffs, stat) + self.getMaxStatsFrom(self.inventory, stat))
		
	def getMaxStatsFrom(self, section, stat):
		c = 0
		for item in section:
			current = getattr(item, stat)
			if (current > c):
				c = current
		return c

		


	# Functions for calculating strength
	def getBaseStr(self):
		return int(math.floor(self.baseStr + self.gainStr * (self.level - 1)))

	def getBonusStr(self):
		return int(math.floor(self.getBonusStats("strength")))

	def getTotalStr(self):
		return int(math.floor(self.getBaseStr() + self.getBonusStats("strength")))

	# Functions for calculating agility
	def getBaseAgi(self):
		return int(math.floor(self.baseAgi + self.gainAgi * (self.level - 1)))
		
	def getBonusAgi(self):
		return int(math.floor(self.getBonusStats("agility")))

	def getTotalAgi(self):
		return int(math.floor(self.getBaseAgi() + self.getBonusStats("agility")))

	# Functions for calculating intel
	def getBaseInt(self):
		return int(math.floor(self.baseInt + self.gainInt * (self.level - 1)))
		
	def getBonusInt(self):
		return int(math.floor(self.getBonusStats("intel")))

	def getTotalInt(self):
		return int(math.floor(self.getBaseInt() + self.getBonusStats("intel")))

	# Funcs for getting primary attribute
	def getStartingPrimary(self):
		if (self.primaryAttr == const.attrStr):
			return self.baseStr
		elif (self.primaryAttr == const.attrAgi):
			return self.baseAgi
		elif (self.primaryAttr == const.attrInt):
			return self.baseInt
		else:
			raise ValueError("Primary Attribute is Invalid")

	def getTotalPrimary(self):
		if (self.primaryAttr == const.attrStr):
			return self.getTotalStr()
		elif (self.primaryAttr == const.attrAgi):
			return self.getTotalAgi()
		elif (self.primaryAttr == const.attrInt):
			return self.getTotalInt()
		else:
			raise ValueError("Primary Attribute is Invalid")

	# Damage stuff
	def getStartingDamage(self):
		return (self.baseDmgMin + self.baseDmgMax) / 2. + self.getStartingPrimary()
	
	def getBaseDamage(self):
		return (self.baseDmgMin + self.baseDmgMax) / 2. + self.getTotalPrimary() + self.getBonusStats("baseDmg")

	def getMinBaseDamage(self):
		return (self.baseDmgMin) + self.getTotalPrimary() + self.getBonusStats("baseDmg")
	
	def getMaxBaseDamage(self):
		return (self.baseDmgMax) + self.getTotalPrimary() + self.getBonusStats("baseDmg")
		
	def getTotalDamage(self):
		return self.getBaseDamage() * (1 + self.getBonusStats("damageP")) + self.getBonusStats("damage")

	def getBonusDamage(self):
		return self.getBaseDamage() * (self.getBonusStats("damageP")) + self.getBonusStats("damage")

	def getDamageSpread(self):
		return self.baseDmgMax - self.baseDmgMin

	# Attack speed
	def getTotalIAS(self):
		return self.getTotalAgi() + self.getBonusStats("IAS") + self.getBonusStats("IASy")

	def getBaseIAS(self):
		return self.getTotalAgi()
	
	def getIllusionIAS(self):
		return self.getTotalAgi + self.getBonusStats("IASy")

	def getBAT(self):
		alternateBAT = self.getMaxStats("BAT")
		if alternateBAT == 0:
			return self.BAT
		else:
			return alternateBAT

	def getAttacksPerSecond(self):
		return (1 + self.getTotalIAS() / 100.) / self.getBAT()

	# Armor stuff
	def getStartingArmor(self):
		return (self.baseArmor - 1 + .14 * self.baseAgi)

	def getBaseArmor(self):
		return (self.baseArmor - 1 + .14 * self.getTotalAgi() + self.getBonusStats("baseArmor"))

	def getTotalArmor(self):
		return self.getBaseArmor() + self.getBonusStats("armor") + self.getMaxStats("armorAura")

	def getBonusArmor(self):
		return self.getBonusStats("armor") + self.getMaxStats("armorAura")

	# HP
	def getStartingHP(self):
		return (self.baseHP + self.baseStr * 19)

	def getTotalHP(self):
		return (self.baseHP + self.getTotalStr() * 19 + self.getBonusStats("HP"))

	# MP
	def getStartingMP(self):
		return (self.baseMP + self.baseInt * 13)

	def getTotalMP(self):
		return (self.baseMP + self.getTotalInt() * 19 + self.getBonusStats("MP"))

	# HP regen
	def getStartingHPR(self):
		return (self.baseHPR + self.baseStr * .03)

	def getTotalHPR(self):
		return (self.baseHPR + self.getTotalStr() * .03 + self.getBonusStats("HPR"))

	# MP regen
	def getStartingMPR(self):
		return (self.baseMPR + self.baseInt * .04)
	
	def getTotalMPR(self):
		baseValue = self.baseMPR + self.getTotalInt() * .04 + self.getBonusStats("baseMPR")
		return baseValue * (1 + self.getBonusStats("MPRP")) + self.getBonusStats("MPR")

	# Movespeed
	def getBaseMS(self):
		return self.MS;

	def getTotalMS(self):
		return (self.MS + self.getBonusStats("MS")) * (1 + self.getBonusStats("MSP") + self.getMaxStats("MSY"))

	# Attack range
	def getStartingAttackRange(self):
		return self.attackRange

	def getTotalAttackRange(self):
		return self.attackRange + self.getBonusStats("rangeInc")

	# Spell Resistance
	def getSpellResistance(self):
		return (1 - ((1 - self.baseSpellRes) * self.getItemSpellResistance() * self.getAbilitySpellResistance() * self.getBuffSpellResistance()))

	def getItemSpellResistance(self):
		return 1 - self.getMaxStatsFrom(self.inventory, "spellRes")

	def getAbilitySpellResistance(self):
		c = 1
		for item in self.abilities:
			c = c * (1 - getattr(item, "spellRes"))
		return c

	def getBuffSpellResistance(self):
		c = 1
		for item in self.buffs:
			c = c * (1 - getattr(item, "spellRes"))
		return c

	# Attack animation stuff

	def getEffectiveFrontSwing(self):
		return self.attackFront / (1 + self.getTotalIAS() / 100)

	def getEffectiveBackSwing(self):
		return self.attackBack / (1 + self.getTotalIAS() / 100)

	# Sight range
	def getDaySight(self):
		return self.daySight + self.getBonusStats("daySight")

	def getNightSight(self):
		return self.nightSight + self.getBonusStats("nightSight")

	# Evasion
	def getEvasion(self):
		return self.getMaxStats("evasion")

	# Crits, bashes
	def getCrits(self):
		c = []
		for item in (self.abilities + self.buffs + self.inventory):
			if item.crit.__class__ == crit:
				c.append(item.crit)
		return c

	def getAverageCrit(self):
		c = 1
		crits = self.getCrits()
		crits.sort(key = lambda x: x.critMult, reverse = True)
		for crit in crits:
			c = (crit.critMult * crit.critChance) + ((1 - crit.critChance) * c)
		return c
		
		
	def __init__(self):

		# User-set parameters
		self.abilities = []
		self.inventory = []
		self.buffs = []
	
		self.level = 1


class modifier:

	name = ""
	description = ""
	icon = ""
	alternateNames = []
	maxLevel = 0

	strength = 0
	agility = 0
	intel = 0

	damage = 0		# Raw damage
	damageP = 0		# %-based damage
	baseDmg = 0	# Base damage upgrades (grow, etc)
	IAS = 0			# Raw attack speed
	IASy = 0			# Raw attack speed that works on illusions (yasha etc)
	BAT = 0 			# BAT changes go here (use the new BAT as your number)
	rangeInc = 0 	# Range upgrades

	HP = 0 		# HP, MP, HP Regen, Mana regen, % mana regen
	MP = 0
	HPR = 0
	MPR = 0
	MPRP = 0

	baseHPR = 0	# Things that increase base HP/Mana regen, like
	baseMPR = 0	# Chemical Rage

	armor = 0		# Raw armor
	armorAura = 0	# Non-stacking armor
	baseArmor = 0	# Base armor upgrades (Dragon Blood)
	spellRes = 0	# Spell resistance (cloak is .15, hood is .3, etc)

	MS = 0	# Movespeed
	MSB = 0	# Movespeed from boots (doesn't stack)
	MSP = 0	# Movespeed %
	MSY = 0	# Movespeed % from Yasha-based items (doesn't stack)
	forceBMS = False	# Not implemented yet
	forceBMSValue = 0	# Will be used for things that alter your
							# base MS, like Hex. 

	daySight = 0
	nightSight = 0

	crit = None
	
	bash = None

	# Damage block
	blockAmountRangeHero = 0
	blockAmountRangeUnit = 0
	blockChanceRangeHero = 0
	blockChanceRangeUnit = 0
	blockAmountMeleeHero = 0
	blockAmountMeleeUnit = 0
	blockChanceMeleeHero = 0
	blockChanceMeleeUnit = 0

	evasion = 0

	auraLifeSteal = 0

	associatedAbilities = []
	associatedBuffs = []

	dissassemble = False

	options = {}

	image = None

	def setLevel(self, lvl):
		if (lvl > self.maxLevel or lvl < 0):
			raise IndexError("Level out of bounds")
		else:
			self.level = lvl

	def isLeveled(self):
		if (self.maxLevel > 0):
			return True
		else:
			return False

	def initOptions(self):
		pass

	def __init__(self, level = None):

		self.associatedHero = None

		if level != None:
			self.setLevel(level)
		else:
			if self.maxLevel == 0:
				self.level = 0
			else:
				self.level = 1
		self.initOptions()
	
class item(modifier):
	cost = 0
	recipeCost = 0
	components = []
	componentOf = []

class recipe(item):
	cost = 0


class ability(modifier):
	level = 1
	abilityClass = None	# Passive, active, toggle, etc
	abilityTarget = None	# Unit target, point target, etc
	abilityType = None	# Physical, Magical, Universal
	duration = None		# The rest of these properties should be self-explanatory
	damageType = None
	cooldown = None
	manaCost = None
	castRange = None		# Use -1 for global
	aoe = None
	affectsBuildings = False
	affectsMechanical = False	# Catapults, some spells like cottle blast damage these
	affectsAncients = False		# Note that most ancients are also magic immune so for magic abilities this only affects the small dragons

class abilitySet():
	abilities = []

class buff(modifier):
	duration = 0
	
class modifierOption():
	displayName = ""
	allowedOptions = None

	def __init__(self):
		self.value = 0

class booleanOption(modifierOption):
	def __init__(self):
		self.value = False

class choiceOption(modifierOption):
	displayValues = []

class numericalOption(modifierOption):
	minValue = 0
	maxValue = 1

class crit():
	critMult = 1
	critChance = 0
	def __init__(self, mult, chance):
		self.critMult = mult
		self.critChance = chance

class bash():
	bashChance = 0
	bashDamage = 0
	bashType = const.abilityPhys
	bashDmgType = const.dmgMagic
	bashDur = 0
	bashCD = 0
	def __init__(self, damage, chance, damageType, bashType, bashDur, bashCD = 0):
		self.bashChance = chance
		self.bashDamage = damage
		self.bashType = bashType
		self.bashDmgType = damageType
		self.bashDur = bashDur
		self.bashCD = bashCD

class itemShop():
	def __init__(self, name, items):
		self.name = name
		self.items = items

class block():
	def __init__(self, block_amount_hero_ranged, block_amount_creep_ranged, block_chance_hero_ranged, block_chance_creep_ranged, block_amount_hero_melee, block_amount_creep_melee, block_chance_hero_melee, block_chance_creep_melee):
		self.block_amount_hero_ranged = block_amount_hero_ranged
		self.block_amount_creep_ranged = block_amount_creep_ranged
		self.block_chance_hero_ranged = block_chance_hero_ranged
		self.block_chance_creep_ranged = block_chance_creep_ranged
		self.block_amount_hero_melee = block_amount_hero_melee
		self.block_amount_creep_melee = block_amount_creep_melee
		self.block_chance_hero_melee = block_chance_hero_melee
		self.block_chance_creep_melee = block_chance_creep_melee


class cleave():
	def __init__(self, amount, aoe, cooldown = 0):
		self.amount = amount
		self.aoe = aoe
		self.cooldown = cooldown

class barrage(): # Flak, splitshot
	def __init__(self, targets, aoe, pctDmg):
		self.targets = targets
		self.aoe = aoe
		self.pctDmg = pctDmg

class bounce():
	def __init__(self, targets, aoe, multiplier = 1):
		self.targets = targets
		self.aoe = aoe
		self.multiplier = multiplier

class psiBlades():
	def __init__(self, amount, length, width):
		self.amount = amount
		self.length = length
		self.width = width

class splash():
	def __init__(self, amounts, aoes):
		self.amounts = amounts
		self.aoes = aoes

class damageInstance():
	def __init__(self, amount, dmgType, evadable = False):
		self.amount = amount
		self.dmgType = dmgType
		self.evadable = evadable

class attackModifier():
	def __init__(self, dmgInstance, abilityType, stackMask = 0, procChance = 1, priority = 0, secondaryTargets = 0, searchRadius = 0, buffApplied = None):
		self.dmgInstance = dmgInstance
		self.stackMask = stackMask
		self.procChance = procChance
		self.priority = priority
		self.secondaryTargets = secondaryTargets
		self.searchRadius = searchRadius
		self.buffApplied = buffApplied
		self.abilityType = abilityType
	


#class aoeAttackInstance():
#	def __init__(self, amount, aoe):
#		self.amount = amount
#		self.aoe = aoe
#
#class aoeAttack():
#	def __init__(self, amount, aoe, dmgType, reducePrimary, evasionPrimaryPct, primaryAffectedOnMiss = False, maxTargets = -1):
#		self.instances = [aoeAttackInstance(amount, aoe)]
#		self.dmgType = dmgType
#		self.reducePrimary = reducePrimary
#		self.evasionPrimaryPct = evasionPrimaryPct
#		self.primaryAffectedOnMiss = primaryAffectedOnMiss
#		self.maxTargets = maxTargets
#
#class splash(aoeAttack):
#	def __init__(self, amounts, aoes):
#		self.instances = []
#		for i in len(amounts):
#			self.instances.append(aoeAttackInstance(amounts[i], aoes[i]))
#		self.dmgType = const.dmgPhys
#		self.reducePrimary = false
#		self.evasionPrimaryPct = .5
#		self.primaryAffectedOnMiss = True
#		self.maxTargets = -1
#
#class bounce(aoeAttack):
#	def __init__(self, amounts, aoes):
#		self.instances = 
